--[[
Copyright (c) 2012 Hello!Game

Permission is hereby granted, free of charge, to any person obtaining a copy
of newinst software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and newinst permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]

----------------------------------------------------------------
-- write by xerysherry

local floor = math.floor;
local random = math.random;
local max = math.max;
local abs = math.abs;
local sqrt = math.sqrt;

local tinsert = table.insert;

local lg = love.graphics;

local gameinfo = {
	cv = nil,
	width = 0, 
	height = 0,
	pause = true,
	iw = 0,
	ih = 0,
	
	hp = 20,
	money = 50,
	
	-- 鼠标指向的块
	mbx = nil,
	mby = nil,
	
	-- 下一波等待
	nextroundtimer = 100,	
	nextroundtic = 0,
	
	-- 下一波添加
	entrytimer = 5,
	entrytic = 0,
	
	-- 添加数
	entry_total = 10,
	entry_num = 10,
	
	towerlist = {},
};

local idxinfo = {
	road = 0,
	block = 1,
	tower = 2,
}

local imageinfo = {
	block = nil,
	tower = nil,
	enemy = nil,
}

local mapinfo = {
	map = {};
	cv = nil;
	update = true;
}

local enemyinfo = {
	-- 起始点，结束点
	rstart = nil,
	rend = nil,
	
	round = 0,
	goal = 0,
	kill = 0,
	
	elist = {},
	
	-- 通路
	road = nil,
	road_update = true;
	roadcv = nil,
}

local astar = Girl.AStar();
local blockn = 16;


----------------------------------------------------------------
-- Enemy
local Enemy =
{
	hp = 0,
	maxhp = 0,
	mov = 0,
	offloc = 0,
	roadidx = 1, 
	road = nil,
};
Enemy.__mode = "k";
Enemy.__index = Enemy;

function Enemy:new(mov, road, round)
	local e = {};
	setmetatable(e, self);
	
	e.road = road;
	e.hp = round*15;
	e.maxhp = e.hp;
	e.mov = mov;
	return e;
end

function Enemy:move()
	local rbc = self.road[self.roadidx];
	local rbn = self.road[self.roadidx+1];
	if rbc==nil then
		return false;
	end
	
	local ne = 0;
	if rbn==nil then
		ne = gameinfo.iw;
	else
		ne = abs(rbc[1]-rbn[1])*gameinfo.iw+abs(rbc[2]-rbn[2])*gameinfo.ih;
	end
	
	self.offloc = self.offloc+self.mov;
	if self.offloc>=ne then
		self.offloc=0;
		self.roadidx=self.roadidx+1;
	end
	return true;
end

function Enemy:getLoc()
	local rbc = self.road[self.roadidx];
	local rbn = self.road[self.roadidx+1];
	if rbc==nil then
		return nil, nil;
	end
	
	local ne = 0;
	if rbn==nil then
		return (rbc[1]-1)*gameinfo.iw+self.offloc, (rbc[2]-1)*gameinfo.ih;
	else
		return (rbc[1]-1)*gameinfo.iw+(rbn[1]-rbc[1])*self.offloc, 
			(rbc[2]-1)*gameinfo.ih+(rbn[2]-rbc[2])*self.offloc;
	end
end

function Enemy:draw()
	local x, y = self:getLoc();
	if x~=nil then
		lg.setColor({255,255,255,(255/self.maxhp*self.hp)});
		--lg.setColor({255,255,255});
		lg.draw(imageinfo.enemy, x, y);
		lg.setColor({(255-255/self.maxhp*self.hp),(255/self.maxhp*self.hp),0});
		lg.rectangle("fill", x, y, gameinfo.iw*(self.hp/self.maxhp), 1)
		--lg.print(tostring(self.hp), x, y)
	end
end

function Enemy:beAttacked(atk)
	self.hp = self.hp-atk;
	if self.hp<0 then
		self.hp=0;
	end
end

function Enemy:isoccupied(x, y)
	local rbc = self.road[self.roadidx];
	local rbn = self.road[self.roadidx+1];
	
	if rbc~=nil then
		if x==rbc[1] and y==rbc[2] then
			return true;
		end
	end
	if rbn~=nil then
		if x==rbn[1] and y==rbn[2] then
			return true;
		end 
	end
	return false;
end

function Enemy:isdead()
	return self.hp<=0;
end

function Enemy:refindroad()
	local rbc =  self.road[self.roadidx];
	if rbc==nil then
		return true;
	end
	
	local newroad = astar:getPath(mapinfo.map, rbc, enemyinfo.rend);
	self.oldroad=nil;
	self.oldroadidx=nil;
	if newroad==nil then
		return false;
	else
		self.oldroad = self.road;
		self.oldroadidx = self.roadidx;
		
		self.road = newroad;
		self.roadidx = 1;
	end
	return true;
end

function Enemy:recoverroad()
	if self.oldroad then
		self.road = self.oldroad;
		self.roadidx = self.oldroadidx;
	end
	self.oldroad = nil;
	self.oldroadidx = nil;
end

function Enemy:findTower()
	local rbc =  self.road[self.roadidx];
	if rbc==nil then
		return;
	end
	
	for i, tower in pairs(gameinfo.towerlist) do
		if tower.target==nil then
			if tower.range>=dist(rbc, tower.loc) then
				tower.target=self;
			end
		end
	end
end

----------------------------------------------------------------
-- Tower
local Tower =
{
	level = 1,
	atk = 0,
	spd = 0,
	spdcount = 0,
	range = 0,
	
	loc = nil,
	target = nil,
	
	drawatk = false;
	
	buildcost = 5,
	levelupcost = 7,
}
Tower.__mode = "k"
Tower.__index = Tower;

function Tower:new(atk, spd, range, loc)
	local t = {};
	setmetatable(t, self);
	
	t.atk = atk;
	t.spd = spd;
	t.range = range;
	t.loc = {unpack(loc)};
	return t;
end

function Tower:attack()
	if self.target==nil then
		return;
	end
	
	if self.target:isdead() or self.target:getLoc()==nil then
		self.target=nil;
		return;
	end
	
	self.spdcount=self.spdcount+1;
	if self.spdcount>self.spd then
		self.spdcount=0;
	end
	
	self.drawatk = true;
	self.target:beAttacked(self.atk)
end

function Tower:levelup()
	self.level = self.level+1;
	self.atk = self.atk*self.level;
end

function Tower:getLoc()
	return (self.loc[1]-1)*gameinfo.iw, 
			(self.loc[2]-1)*gameinfo.ih;
end

function Tower:draw()
	local x, y = self:getLoc();
	if self.drawatk then
		lg.setColor({255,0,255});
	else
		lg.setColor({255,255,255});
	end
	lg.draw(imageinfo.tower, x, y);
	self.drawatk =false;
	lg.setColor({0,0,255});
	lg.print(tostring(self.level), x, y);
	lg.setColor({255,255,255});
end	

----------------------------------------------------------------
function dist(pt1, pt2)
	return math.abs(pt1[1]-pt2[1])+math.abs(pt1[2]-pt2[2]);
end

local function InitMap()
	mapinfo.cv = lg.newCanvas(gameinfo.width, gameinfo.height);
	ClearMap();
end

function InitGame(idx)
	-- 获得游戏画布信息
	gameinfo.cv = graph:getCanvas(idx);
	gameinfo.width, gameinfo.height = graph:getCanvasSize(idx);

	InitMap();
	
	gameinfo.iw, gameinfo.ih = floor(gameinfo.width/blockn), floor(gameinfo.height/blockn);

	imageinfo.block = lg.newCanvas(gameinfo.iw, gameinfo.ih);
	imageinfo.tower = lg.newCanvas(gameinfo.iw, gameinfo.ih);
	imageinfo.enemy = lg.newCanvas(gameinfo.iw, gameinfo.ih);
	enemyinfo.roadcv = lg.newCanvas(gameinfo.width, gameinfo.height);
	print(gameinfo.width, gameinfo.height)

	-- 初始游戏图片
	lg.setCanvas(imageinfo.block);
	lg.setColor({255,255,255});
	lg.rectangle("fill", 0,0, gameinfo.iw, gameinfo.ih);
	lg.setCanvas();
	
	lg.setCanvas(imageinfo.tower);
	lg.setColor({0,255,0});
	lg.rectangle("fill", 0,0, gameinfo.iw, gameinfo.ih);
	lg.setCanvas();
	
	lg.setCanvas(imageinfo.enemy);
	lg.setColor({255,0,0});
	lg.rectangle("fill", 0,0, gameinfo.iw, gameinfo.ih);
	lg.setCanvas();
	
	lg.setColor({255,255,255});
	
	idle:addTask(UpdateGame);
end

function ClearMap()
	for i=1, blockn do
		mapinfo.map[i]={};
		for j=1, blockn do
			mapinfo.map[i][j] = idxinfo.road;
		end
	end
end

function GameStart()
	print("GameStart");

	ClearMap();
	local map = mapinfo.map;
	for i=1, blockn do
		map[1][i] = idxinfo.block;
		map[blockn][i] = idxinfo.block;
	end
	for i=1, blockn do
		map[i][1] = idxinfo.block;
		map[i][blockn] = idxinfo.block;
	end
	
	gameinfo.pause = false;
	mapinfo.update = true;
	
	enemyinfo.rstart = {1,random(2,blockn-1)};
	enemyinfo.rend = {blockn,random(2,blockn-1)};
	
	map[enemyinfo.rstart[1]][enemyinfo.rstart[2]] = idxinfo.road;
	map[enemyinfo.rend[1]][enemyinfo.rend[2]] = idxinfo.road;
	
	enemyinfo.round = 0;
	enemyinfo.road = EnemyRoad();
	enemyinfo.road_update = true;
	
	enemyinfo.elist = {};
	gameinfo.hp = 20;
	gameinfo.towerlist = {};
	gameinfo.money = 50;
end

function GameResume()
	gameinfo.pause = false;
end

function GamePause()
	gameinfo.pause = true;
end

function GameOver()
	if gameinfo.hp>0 then
		return;
	end
	
	GamePause();
	gm:getGui("TitleMenu"):open(1);
end

function EnemyEntry()
	gameinfo.nextroundtic=gameinfo.nextroundtic+1;
	if gameinfo.nextroundtimer > gameinfo.nextroundtic then
		return;
	elseif gameinfo.nextroundtimer == gameinfo.nextroundtic then
		gameinfo.entrytic = 5;
		gameinfo.entry_num = 0;
		enemyinfo.round = enemyinfo.round+1;
	end

	gameinfo.entrytic = gameinfo.entrytic+1;
	if gameinfo.entrytimer<=gameinfo.entrytic then
		tinsert(enemyinfo.elist, 
			Enemy:new(2, enemyinfo.road, enemyinfo.round));
		gameinfo.entry_num = gameinfo.entry_num+1;
		gameinfo.entrytic = 0;
		
		if gameinfo.entry_total<=gameinfo.entry_num then
			gameinfo.nextroundtic = 0;
		end
	end
end

function EnemyAction()
	for i, enemy in pairs(enemyinfo.elist) do
		if enemy:isdead() then
			-- 死亡
			enemyinfo.elist[i]=nil;
			enemyinfo.kill = enemyinfo.kill+1;
			gameinfo.money = gameinfo.money+1;
		else
			if not enemy:move() then
				-- 到达终点
				enemyinfo.elist[i]=nil;
				enemyinfo.goal=enemyinfo.goal+1;
				gameinfo.hp=gameinfo.hp-1;
			else
				enemy:findTower();
			end
		end
	end
end

function EnemyRoad()
	return astar:getPath(mapinfo.map, enemyinfo.rstart, enemyinfo.rend);
end

function BuildTower(x, y)
	if x<=1 or x>=blockn or y<=1 or y>=blockn then
		return false;
	end
	
	if gameinfo.money < Tower.buildcost then
		print("no enough money")
		return;
	end
	
	for i, enemy in pairs(enemyinfo.elist) do
		if enemy:isoccupied(x,y) then
			print("isoccup",enemy:isdead())
			return;
		end
	end
	
	local road = nil;
	if mapinfo.map[x][y]==idxinfo.road then
		mapinfo.map[x][y]=idxinfo.tower;
		road = EnemyRoad();
		if road==nil or (#road)<=1 then
			mapinfo.map[x][y]=idxinfo.road;
			print("no road")
		else
			local result = true;
			local old_road = {};
			for i, enemy in pairs(enemyinfo.elist) do
				if not enemy:refindroad() then
					print("enemy no road", enemy:isdead())
					result = false;
					break;
				end
			end
			if not result then
				for i, enemy in pairs(enemyinfo.elist) do
					enemy:recoverroad();
				end
				print("enemy no road")
				return;
			end
		
			enemyinfo.road = road;
			enemyinfo.road_update = true;
			mapinfo.update = true;
			
			tinsert(gameinfo.towerlist, Tower:new(1, 30, 3, {x, y}));
			gameinfo.money = gameinfo.money - Tower.buildcost;
		end
	elseif mapinfo.map[x][y]==idxinfo.tower then
		
		
		for i, tower in pairs(gameinfo.towerlist) do
			if x==tower.loc[1] and y==tower.loc[2] then
				if gameinfo.money < tower.levelupcost+tower.level then
					return;
				end
				tower:levelup();
				gameinfo.money = gameinfo.money - (tower.levelupcost+tower.level);
				break;
			end
		end
	end
end

function TowerAction()
	for i, tower in pairs(gameinfo.towerlist) do
		tower:attack();
	end
end

function UpdateMap()
	if not mapinfo.update then
		return;
	end

	mapinfo.cv:clear();
	lg.setCanvas(mapinfo.cv);
	lg.setColor({255,255,255});
	for i=1, blockn do
		for j=1, blockn do
			if mapinfo.map[i][j]==idxinfo.block then
				lg.draw(imageinfo.block, (i-1)*gameinfo.iw, (j-1)*gameinfo.ih);
			--elseif mapinfo.map[i][j]==idxinfo.tower then
			--	lg.draw(imageinfo.tower, (i-1)*gameinfo.iw, (j-1)*gameinfo.ih);
			end
		end
	end
	lg.setCanvas();
	mapinfo.update = false;
end

function UpdateRoad()
	if not enemyinfo.road_update then
		return;
	end
	enemyinfo.road_update = false;
	enemyinfo.roadcv:clear();
	
	if enemyinfo.road==nil then
		return;
	end
	
	lg.setCanvas(enemyinfo.roadcv);
	lg.setColor({200,100,100});
	local len = #enemyinfo.road;
	for i=1, len-1 do
		local pt1 = enemyinfo.road[i];
		local pt2 = enemyinfo.road[i+1];
		lg.rectangle("fill",
			(pt1[1]-1)*gameinfo.iw+gameinfo.iw/2, 
			(pt1[2]-1)*gameinfo.ih+gameinfo.ih/2, 
			(pt2[1]-pt1[1])*gameinfo.iw+1-((pt1[1]>pt2[1] and 1) or 0), 
			(pt2[2]-pt1[2])*gameinfo.ih+1-((pt1[2]>pt2[2] and 1) or 0));
	end

	lg.setCanvas();
end

function UpdateUI()
	local ui = gm:getGui("GameUI"):get("bg");
	ui:get("HP"):setLabel("HP: "..gameinfo.hp);
	ui:get("Kill"):setLabel("KILL : "..enemyinfo.kill);
	ui:get("Money"):setLabel("$ "..gameinfo.money);
	ui:get("Round"):setLabel("Round: "..enemyinfo.round);
end

function UpdateGame()
	if gameinfo.pause then
		return false;
	end

	EnemyEntry();
	EnemyAction();
	TowerAction();
	
	UpdateMap();
	UpdateRoad();
	UpdateUI();
	
	gameinfo.cv:clear();
	
	lg.setCanvas(gameinfo.cv);
	lg.setColor({100,100,100});
	lg.rectangle("fill", 0,0,gameinfo.width, gameinfo.height);
		
	-- 绘制路径
	DrawRoad();

	DrawTower();
	
	-- 绘制敌人
	DrawEnemy();
	
	-- 绘制光标
	DrawCursor();
	
	lg.setColor({255,255,255});
	lg.setCanvas();
	
	GameOver();
	
	return false;
end

function DrawRoad()
	lg.setColor({255,255,255});
	lg.draw(enemyinfo.roadcv,0,0);
end

function DrawTower()
	for i, tower in pairs(gameinfo.towerlist) do
		tower:draw();
	end
end

function DrawEnemy()
	for i, enemy in pairs(enemyinfo.elist) do
		enemy:draw();
	end
end

function DrawCursor()
	lg.setColor({255,255,255});
	lg.draw(mapinfo.cv,0,0);
	if gameinfo.mbx~=nil and gameinfo.mby~=nil then
		
		if mapinfo.map[gameinfo.mbx][gameinfo.mby]==idxinfo.road then
			lg.setColor({0,255,0,200});
			lg.rectangle("fill", 
				(gameinfo.mbx-1)*gameinfo.iw, 
				(gameinfo.mby-1)*gameinfo.ih,
				gameinfo.iw, gameinfo.ih);
		elseif mapinfo.map[gameinfo.mbx][gameinfo.mby]==idxinfo.tower then
			lg.setColor({255,255,0,150});
			lg.circle("fill", 
				(gameinfo.mbx-1)*gameinfo.iw+gameinfo.iw/2, 
				(gameinfo.mby-1)*gameinfo.ih+gameinfo.ih/2, 3*gameinfo.iw);
		end
	end
end

function GameMousePressed(x, y, button)
	BuildTower(floor(x/gameinfo.iw)+1, floor(y/gameinfo.ih)+1);
end

function GameMouseReleased(x, y, button)
	
end

function GameMouseMove(x, y)
	gameinfo.mbx, gameinfo.mby = 
		floor(x/gameinfo.iw)+1, floor(y/gameinfo.ih)+1;
end

function GameMouseLeave()
	gameinfo.mbx, gameinfo.mby = nil, nil
end
